/*
 * Copyright (c) 2022, Chongqing UCchip InfoTech Co.,Ltd
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * @brief HTTP OTA downloader
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-01-17     jlu          the first version
 */

#include "net_down.h"
#include "fal.h"
#include "gateway_manager.h"
#include "manager_queue.h"
#include "manager_queue_comm.h"
#include "module_queue_struct.h"
#include "ota_manager.h"
#include "uc_log.h"
#include "webclient.h"
#include <fcntl.h>
#include <rtthread.h>
#include <stdio.h>
#include <sys/stat.h>
#include <tiny_md5.h>
#include <unistd.h>

#define BUF_SIZE (1024)
const char *partiton_name = EXT_FAL_DOWN_NAME;
const struct fal_partition *g_down_partition = RT_NULL;
/**
 * @brief  下载信息
 *
 */
static down_info_t g_down_info = {0};

static int partition_init(void)
{
    int ret = 0;
    const struct fal_flash_dev *flash_dev = RT_NULL;

    g_down_partition = fal_partition_find(partiton_name);
    if (g_down_partition == RT_NULL)
    {
        TRACE_E("Find g_down_partition (%s) failed!", partiton_name);
        ret = -1;
        return ret;
    }

    flash_dev = fal_flash_device_find(g_down_partition->flash_name);
    if (flash_dev == RT_NULL)
    {
        TRACE_E("Find flash device (%s) failed!", g_down_partition->flash_name);
        ret = -1;
        return ret;
    }
    return ret;
}
INIT_APP_EXPORT(partition_init);

void *get_down_partition(void)
{
    return (void *)g_down_partition;
}

/**
 * @brief  HTTP 分片下载回调函数
 *
 * @param  buffer 接收缓存
 * @param  length 接收长度
 * @return 返回0
 */
static int shard_download(char *buffer, int length)
{
    int ret = RT_EOK;

    RT_ASSERT(buffer != RT_NULL);
    if (length == fal_partition_write(g_down_partition, g_down_info.recv_len, buffer, length))
    {
        g_down_info.cur_rate = g_down_info.recv_len * 100 / g_down_info.file_size;
        if (g_down_info.cur_rate != g_down_info.last_rate)
        {
            TRACE_I("download: %d%%", g_down_info.cur_rate);
        }
        g_down_info.last_rate = g_down_info.cur_rate;
    }
    else
    {
        g_down_info.state = 2;
        ret = -RT_ERROR;
    }
    g_down_info.recv_len += length;
    // 释放内存
    web_free(buffer);
    return ret;
}

/**
 * @brief  HTTP 下载文件
 *
 * @param  url 资源标识符
 * @param  handle_function the handle function
 * @return 成功返回0，失败返回非0
 */
static int download_file(const char *url)
{
    int length = 0;
    rt_err_t ret = RT_EOK;
    struct webclient_session *session = RT_NULL;

    do
    {
        // 创建会话
        session = webclient_session_create(1024);
        if (RT_NULL == session)
        {
            TRACE_E("webclient_session_create failed");
            ret = -RT_ENOMEM;
            break;
        }

        // 获取文件长度
        ret = webclient_shard_head_function(session, url, &length);
        if (ret < 0)
        {
            TRACE_E("webclient_shard_head_function error");
            ret = -RT_ERROR;
            break;
        }

        TRACE_I("waiting for fal partition erase size %d, it may take about 10s", length);
        if (fal_partition_erase(g_down_partition, 0, length) < 0)
        {
            TRACE_E("fal_partition_erase failed, partition name: %s, file size: %d", g_down_partition->name, length);
            ret = -RT_ERROR;
            break;
        }

        // 初始化下载信息
        g_down_info.recv_len = 0;
        g_down_info.file_size = length;

        // 注册分片下载回调函数
        webclient_register_shard_position_function(session, shard_download);

        // HTTP 断点下载
        ret = webclient_shard_position_function(session, url, 0, length, DOWN_BLOCK_SIZE);
        if (WEBCLIENT_OK != ret)
        {
            TRACE_E("webclient_downloader() error, code=%d.", ret);
            ret = -RT_ERROR;
            break;
        }

        ret = RT_EOK;
    } while (0);

    // 注销分片下载回调函数
    webclient_register_shard_position_function(session, RT_NULL);
    // 关闭会话
    if (RT_NULL != session)
    {
        webclient_close(session);
        session = RT_NULL;
    }

    return ret;
}

/**
 * @brief  MD5 校验
 *
 * @return 成功返回0，否则返回非0
 */
static int md5_verify(int file_size)
{
    unsigned char *buffer = rt_malloc(BUF_SIZE);
    int size = 0;
    int len = 0;
    tiny_md5_context ctx = {0};
    unsigned char val[16] = {0};
    char file_md5[33] = {0};

    // 初始化 MD5 上下文
    tiny_md5_starts(&ctx);
    do
    {
        size = fal_partition_read(g_down_partition,
                                  len,
                                  buffer,
                                  (file_size - len >= BUF_SIZE) ? BUF_SIZE : (file_size - len));
        if (size > 0)
        {
            // 更新 MD5 值
            tiny_md5_update(&ctx, buffer, size);
            len += size;
        }
    } while ((len < file_size) && (size > 0));

    rt_free(buffer);

    if (len >= file_size)
    {
        // 最终摘要
        tiny_md5_finish(&ctx, val);

        // MD5校验
        rt_snprintf(file_md5, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                    val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7],
                    val[8], val[9], val[10], val[11], val[12], val[13], val[14], val[15]);
        if (0 == rt_strncmp(g_down_info.md5, file_md5, 32))
            return 0;
    }
    TRACE_E("error file_size %d MD5: %s", file_size, file_md5);
    return 2;
}

/**
 * @brief  下载完成，通知网关任务
 *
 * @param  param 参数
 * @return 成功放回0，否则返回非0
 */
static int download_finish(void)
{
    void *queue = get_gateway_manager_queue();
    if (NULL == queue)
    {
        TRACE_E("%s, %d get_gateway_manager_queue failed", __FUNCTION__, __LINE__);
        return 1;
    }

    ota_upgrade_res_t *data = rt_malloc(sizeof(ota_upgrade_res_t));
    if (NULL == data)
    {
        TRACE_E("%s, %d malloc failed", __FUNCTION__, __LINE__);
        return 2;
    }
    // 通知网关进行OTA升级
    data->download_state = 0;
    data->file_size = g_down_info.file_size;

    if (rt_strlen(g_down_info.name) > sizeof(data->file_name) - 4)
    {
        TRACE_E("file_name too long");
        rt_free(data);
        return 2;
    }

    rt_strcpy(data->file_name, g_down_info.name);

    if (module_queue_communication(queue, NET_APP_MODULE, GATEWAY_MANAGER_MODULE, OTA_UPGRADE_RESPONSE, (void *)data))
    {
        rt_free(data);
        return 3;
    }
    return 0;
}

/**
 * @brief  HTTP 下载入口
 *
 * @param  param 参数
 */
static void download_entry(void *param)
{
    int res, down_cnt;
    struct webclient_session *session = RT_NULL;
    RT_ASSERT(g_down_partition);

    for (down_cnt = 0; down_cnt < DOWN_RETRY; down_cnt++)
    {
        TRACE_I("%d dl name %s", down_cnt + 1, g_down_info.name);
        TRACE_I("url %s", g_down_info.url);

        // 文件下载
        res = download_file(g_down_info.url);
        if (RT_EOK == res)
        {
            // MD5 验证
            if (0 == md5_verify(g_down_info.file_size))
            {
                TRACE_I("http download success");
                // 下载成功，通知进行 OTA 升级
                if (0 != download_finish())
                {
                    TRACE_E("download_finish error");
                    manager_set_ota_state(OTA_STATE_STOP);
                }
                break;
            }
        }
        else
        {
            TRACE_E("download_file res %d", res);
        }

        // 延时后重试
        rt_thread_delay(DOWN_DELAY);
    }

    if (down_cnt == DOWN_RETRY)
    {
        manager_set_ota_state(OTA_STATE_STOP);
    }

    // 释放资源
    if (RT_NULL != g_down_info.url)
    {
        rt_free(g_down_info.url);
        g_down_info.url = RT_NULL;
    }
    if (RT_NULL != g_down_info.name)
    {
        rt_free(g_down_info.name);
        g_down_info.name = RT_NULL;
    }
    g_down_info.state = 0;
    g_down_info.cur_rate = 0;
    g_down_info.last_rate = 0;
}

/**
 * @brief  http 下载
 *
 * @param  uri 资源标识符
 * @param  name 文件名
 * @param  md5 文件MD5值
 * @return 成功返回0，否则返回非0
 *         1. 非法参数
 *         2. 正在下载中
 *         3. 创建线程失败
 */
int http_download(const char *uri, const char *name, const char *md5)
{
    const char tname[RT_NAME_MAX] = "down";

    if (RT_NULL == uri || RT_NULL == name || RT_NULL == md5 || rt_strlen(md5) < 32)
        return 1;

    if (g_down_info.state == 1)
        return 2;

    // 下载状态
    g_down_info.state = 1;

    // 资源标识符
    g_down_info.url = rt_malloc(rt_strlen(uri) + 1);
    if (RT_NULL == g_down_info.url)
    {
        TRACE_E("%s, %d malloc failed", __FUNCTION__, __LINE__);
        return 3;
    }
    rt_memcpy(g_down_info.url, uri, rt_strlen(uri) + 1);

    // 文件名
    g_down_info.name = rt_malloc(rt_strlen(name) + 8);
    if (RT_NULL == g_down_info.name)
    {
        TRACE_E("%s, %d malloc failed", __FUNCTION__, __LINE__);
        rt_free(g_down_info.url);
        g_down_info.url = RT_NULL;
        return 3;
    }
    rt_snprintf(g_down_info.name, rt_strlen(name) + 8, "/root/%s", name);

    // MD5 摘要
    rt_memcpy(g_down_info.md5, md5, 32);
    g_down_info.md5[32] = '\0';

    // 创建线程
    rt_thread_t tid = rt_thread_create(tname, download_entry, RT_NULL, DOWN_THREAD_STACK_SIZE, DOWN_THREAD_PRIORITY, DOWN_THREAD_TICK);
    if (tid != 0)
    {
        rt_thread_startup(tid);
        return 0;
    }

    return 4;
}
